package com.giteelou.simple;

import java.util.ArrayList;
import java.util.List;

/**
 * 手工打造的（简单的）词法分析器
 * 词法分析器 Lexer (近义词 Lexical Analyzer)
 * 构造有限自动机
 */
public class SimpleLexer {

    public static void main(String[] args) {

        SimpleLexer lexer = new SimpleLexer();
        lexer.analyze("age >= 5;");
        lexer.print();

        lexer.analyze("age > 5;");
        lexer.print();

        lexer.analyze("int age = 5;");
        lexer.print();

        lexer.analyze("inta_ age = 45;");
        lexer.print();

        lexer.analyze("in age = 45;");
        lexer.print();

        lexer.analyze("2 + 3 * 5;");
        lexer.print();

    }

    private DfaState state;
    private SimpleToken token;
    private StringBuilder tokenText;
    private List<Token> tokens;
    private String statement;

    /**
     * 对语句进行词法分析。
     * 这是一个有限状态自动机，在不同状态中迁移。
     *
     * @param statement 语句
     */
    public List<Token> analyze(String statement) {
        this.statement = statement;
        tokens = new ArrayList<>();
        tokenText = new StringBuilder();
        state = DfaState.Initial;
        char[] array = statement.toCharArray();
        for (char c : array) {
            analyze(c);
        }
        // 最后剩下的token
        if (tokenText.length() > 0) {
            token.text = tokenText.toString();
            tokens.add(token);
        }
        return tokens;
    }

    /**
     * 逐字符分析
     *
     * @param c 字符
     */
    private void analyze(char c) {
        switch (state) {
            case Initial:
            case Semicolon:
            case LeftParen:
            case RightParen:
            case Plus:
            case Minus:
            case Star:
            case Slash:
            case Assignment:
            case GE:
                state = initToken(c);
                break;
            case Int_i:
                if (c == 'n') {
                    state = DfaState.Int_n;
                    tokenText.append(c);
                } else if (isAlpha(c) || c == '_' || isDigit(c)) {
                    tokenText.append(c);
                    state = DfaState.ID;
                } else {
                    state = initToken(c);
                }
                break;
            case Int_n:
                if (c == 't') {
                    state = DfaState.Int_t;
                    tokenText.append(c);
                } else if (isAlpha(c) || c == '_' || isDigit(c)) {
                    tokenText.append(c);
                    state = DfaState.ID;
                } else {
                    state = initToken(c);
                }
                break;
            case Int_t:
                if (isBlank(c)) {
                    token.type = TokenType.Int;
                    state = initToken(c);
                } else {
                    tokenText.append(c);
                    state = DfaState.ID;
                }
                break;
            case ID:
                if (isAlpha(c) || c == '_' || isDigit(c)) {
                    tokenText.append(c);
                } else {
                    state = initToken(c);
                }
                break;
            case GT:
                if (c == '=') {
                    state = DfaState.GE;
                    token.type = TokenType.GE;
                    tokenText.append(c);
                } else {
                    state = initToken(c);
                }
                break;
            case IntLiteral:
                if (isDigit(c)) {
                    tokenText.append(c);
                } else {
                    state = initToken(c);
                }
                break;
        }
    }

    /**
     * 有限状态机进入初始状态。
     * 这个初始状态其实并不做停留，它马上进入其他状态。
     * 开始解析的时候，进入初始状态；
     * 某个Token解析完毕，也进入初始状态，在这里把Token记下来，然后建立一个新的Token。
     *
     * @param c 字符
     * @return 有限自动机状态
     */
    private DfaState initToken(char c) {
        state = DfaState.Initial;
        if (tokenText.length() > 0) {
            token.text = tokenText.toString();
            tokens.add(token);
        }
        token = new SimpleToken();
        tokenText.delete(0, tokenText.length());
        if (isAlpha(c) || c == '_') {
            if (c == 'i') {
                state = DfaState.Int_i;
            } else {
                state = DfaState.ID;
            }
            token.type = TokenType.Identifier;
            tokenText.append(c);
        } else if (isDigit(c)) {
            state = DfaState.IntLiteral;
            token.type = TokenType.IntLiteral;
            tokenText.append(c);
        } else if (c == '>') {
            state = DfaState.GT;
            token.type = TokenType.GT;
            tokenText.append(c);
        } else if (c == '=') {
            state = DfaState.Assignment;
            token.type = TokenType.Assignment;
            tokenText.append(c);
        } else if (c == '+') {
            state = DfaState.Plus;
            token.type = TokenType.Plus;
            tokenText.append(c);
        } else if (c == '-') {
            state = DfaState.Minus;
            token.type = TokenType.Minus;
            tokenText.append(c);
        } else if (c == '*') {
            state = DfaState.Star;
            token.type = TokenType.Star;
            tokenText.append(c);
        } else if (c == '/') {
            state = DfaState.Slash;
            token.type = TokenType.Slash;
            tokenText.append(c);
        } else if (c == ';') {
            state = DfaState.Semicolon;
            token.type = TokenType.Semicolon;
            tokenText.append(c);
        } else if (c == '(') {
            state = DfaState.LeftParen;
            token.type = TokenType.LeftParenthesis;
            tokenText.append(c);
        } else if (c == ')') {
            state = DfaState.RightParen;
            token.type = TokenType.RightParenthesis;
            tokenText.append(c);
        }
        return state;
    }

    /**
     * 打印词法分析结果
     */
    public void print() {
        System.out.println("语句: " + statement + "\n词法分析结果:");
        tokens.forEach(e -> System.out.printf("%-10s\t\t%-10s\n", e.getType(), e.getText()));
        System.out.println("---------------");
    }

    /**
     * 字符是否是空格字符
     *
     * @param c 字符
     * @return true or false
     */
    private boolean isBlank(char c) {
        return c == ' ';
    }

    /**
     * 字符是否是数字
     *
     * @param c 字符
     * @return true or false
     */
    private boolean isDigit(char c) {
        return c >= '0' && c <= '9';
    }

    /**
     * 字符是否是字母
     *
     * @param c 字符
     * @return true or false
     */
    private boolean isAlpha(char c) {
        return c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z';
    }

    /**
     * 有限自动机状态
     */
    private enum DfaState {
        /**
         * 初始状态
         */
        Initial,
        /**
         * 标识符
         */
        ID,
        /**
         * 大于
         */
        GT,
        /**
         * 大于等于
         */
        GE,
        /**
         * 数字(整型)字面量
         */
        IntLiteral,
        /**
         * 赋值符号
         */
        Assignment,
        /**
         * int关键字i字符
         */
        Int_i,
        /**
         * int关键字n字符
         */
        Int_n,
        /**
         * int关键字t字符
         */
        Int_t,
        /**
         * +号
         */
        Plus,
        /**
         * -号
         */
        Minus,
        /**
         * *号
         */
        Star,
        /**
         * /号
         */
        Slash,
        /**
         * 左括弧
         */
        LeftParen,
        /**
         * 右括弧
         */
        RightParen,
        /**
         * ;号
         */
        Semicolon
    }

    /**
     * 简单实现token
     */
    private static class SimpleToken implements Token {
        private TokenType type;
        private String text;

        @Override
        public TokenType getType() {
            return type;
        }

        @Override
        public String getText() {
            return text;
        }
    }


}
